const FORMAT_SQL = 'sql';
const FORMAT_PARAMETERIZED = 'parameterized';

const isRuleGroup = (ruleOrGroup) => {
    const rg = ruleOrGroup
    return !!(rg.combinator && rg.rules)
}

const mapOperator = (op) => {
    switch (op.toLowerCase()) {
        case 'null':
            return 'is null'
        case 'notnull':
            return 'is not null'
        case 'notin':
            return 'not in'
        case 'contains':
        case 'beginswith':
        case 'endswith':
            return 'like'
        case 'doesnotcontain':
        case 'doesnotbeginwith':
        case 'doesnotendwith':
            return 'not like'
        default:
            return op
    }
}

export const defaultValueProcessor = (
    _field,
    operator,
    value
) => {
    let val = `'${value}'`
    if (operator.toLowerCase() === 'null' || operator.toLowerCase() === 'notnull') {
        val = ''
    } else if (operator.toLowerCase() === 'in' || operator.toLowerCase() === 'notin') {
        val = `(${value
            .split(',')
            .map((v) => `'${v.trim()}'`)
            .join(', ')})`
    } else if (operator.toLowerCase() === 'contains' || operator.toLowerCase() === 'doesnotcontain') {
        val = `'%${value}%'`
    } else if (
        operator.toLowerCase() === 'beginswith' ||
        operator.toLowerCase() === 'doesnotbeginwith'
    ) {
        val = `'${value}%'`
    } else if (operator.toLowerCase() === 'endswith' || operator.toLowerCase() === 'doesnotendwith') {
        val = `'%${value}'`
    } else if (typeof value === 'boolean') {
        val = `${value}`.toUpperCase()
    }
    return val
}

/**
 * Formats a query in the requested output format.  The optional
 * `valueProcessor` argument can be used to format the values differently
 * based on a given field, operator, and value.  By default, values are
 * processed assuming the default operators are being used.
 */
const formatQuery = (ruleGroup, options) => {
    let format
    let valueProcessor
    let quoteFieldNamesWith

    if (typeof options === 'string') {
        format = options
        valueProcessor = defaultValueProcessor
        quoteFieldNamesWith = ''
    } else {
        format = options.format || FORMAT_SQL
        valueProcessor = options.valueProcessor || defaultValueProcessor
        quoteFieldNamesWith = options.quoteFieldNamesWith || ''
    }

    const formatLowerCase = format.toLowerCase()

    if (formatLowerCase === FORMAT_SQL || formatLowerCase === FORMAT_PARAMETERIZED) {
        const parameterized = formatLowerCase === FORMAT_PARAMETERIZED
        const params = []

        const processRule = (rule) => {
            const value = valueProcessor(rule.field, rule.operator, rule.value)
            const operator = mapOperator(rule.operator)
            const field = rule.ignoreCase ? `lower(${rule.field})` : rule.field
            if (parameterized && value) {
                if (operator.toLowerCase() === 'in' || operator.toLowerCase() === 'not in') {
                    const splitValue = (`${rule.value}`).split(',').map((v) => v.trim())
                    splitValue.forEach((v) => params.push(v))
                    return `${quoteFieldNamesWith}${rule.field
                        }${quoteFieldNamesWith} ${operator} (${splitValue.map(() => '?').join(', ')})`
                }
                params.push((`${value}`).match(/^'?(.*?)'?$/)[1])
            }
            return `${quoteFieldNamesWith}${field}${quoteFieldNamesWith} ${operator} ${parameterized && value ? '?' : value
                }`.trim()
        }

        const processRuleGroup = (rg) => {
            const processedRules = rg.rules.map((rule) => {
                if (isRuleGroup(rule)) {
                    return processRuleGroup(rule)
                }
                return processRule(rule)
            })
            return `${rg.not ? 'NOT ' : ''}(${processedRules.join(` ${rg.combinator} `)})`
        }

        if (parameterized) {
            return { sql: processRuleGroup(ruleGroup), params }
        } else {
            return processRuleGroup(ruleGroup)
        }
    } else {
        return ''
    }
}

export default formatQuery
